package DispatchSys;

import java.util.concurrent.Callable;
import java.util.concurrent.CompletionService;
import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;

import Shared.Utils;

/**
 * The AssignmentManager module, see Architecture report for specifications.
 * 
 */

public class AssignmentManager implements Runnable {

	private final long TIMEOUT = 10000L; // 10 000 milisec.

	private Assigner assigner;
	private Database db;
	private AmbulanceManager am;

	private ExecutorService threadPool;
	private CompletionService<Assignment> pool;

	private static AssignmentManager singleton = new AssignmentManager();
	
	/**
	 * 
	 * @return the unique AssignmentManager instance.
	 */
	
	public static AssignmentManager getInstance () {
		return singleton;
	}
	
	private AssignmentManager() {

		this.assigner = new Assigner();
		this.db = Database.getInstance();
		this.am = AmbulanceManager.getInstance();

	}

	/**
	 * Starts creating assigments.
	 */
	public void startDispatching() {
		try {
			threadPool = Executors.newCachedThreadPool();
			pool = new ExecutorCompletionService<Assignment>(threadPool);

			loop();

		} finally {
			threadPool.shutdownNow();
		}
	}

	private void loop() {

		while (true) {
			Assignment assignment;

			// Try to create a new assignment.
			assignment = assigner.assign();
			if (assignment != null) {
				Utils.disp("Got an assignment");
				assignment.getAm().startAssignment();
				assignment.getEr().setAssigned(true);
				pool.submit(new CompletionChecker(db, assignment));
			}

			// Fetch results of previous assignments.
			Future<Assignment> result;
			try {
				result = pool.poll(10, TimeUnit.MILLISECONDS);
			} catch (InterruptedException e1) {
				Thread.currentThread().interrupt();
				break;
			}
			if (result == null)
				continue;

			// Analyze results of previous assignments.
			
			try {
				assignment = result.get();
				if (assignment == null) {
					// Should not happen !
					continue;
				}

				if (assignment.isFinished()) {
					Utils.disp("assignment finished...");
					assignment.getEr().setFinished();
					assignment.getAm().endAssignment();

				} else {
					// TODO : Check ambulance state before reassign.
					// PossibleError :
					// - amb state = Error;
					// c'est tout je crois. (Guillaume)
					// Error ou Timeout ! (?) (Thoralf)
					assignment.getEr().setAssigned(false);

					assignment.getAm().endAssignment();
					if (assignment.getAm().isLogged() ){
						assignment.getAm().unregister();
					}
				}
				am.deleteAssignment(assignment);
			} catch (Exception e) {
				// This should not happen...
				// This is ensured by poll()
				e.printStackTrace();
				break;
			}

		}
	}

	/**
	 * Starts startDispatching().
	 */
	@Override
	public void run() {
		startDispatching();
	}

	private final class CompletionChecker implements Callable<Assignment> {
		private Assignment assignment;

		public CompletionChecker(Database db, Assignment a) {
			this.assignment = a;
		}

		public Assignment call() {
			Thread.currentThread()
					.setName("assign-" + assignment.getAm().getID() + "-" + assignment.getEr().toString());
			Utils.disp("Thread started");
			Assignment a = checkAssign();
			Utils.disp("Thread finished");
			Thread.currentThread().setName("--");
			return a;
		}

		public Assignment checkAssign() {

			AmbulanceInfo amb = assignment.getAm();

			if (!am.addAssignment(assignment, TIMEOUT)) {
				//assignment.setAborted();
				return assignment;
			}

			while (amb.isOnMission() && amb.isLogged()) {
				try {
					Thread.sleep(20);
				} catch (InterruptedException e) {
					Thread.currentThread().interrupt();
					return null;
				}
			}

			Utils.disp("Thread LOOP exit.");

			if (!amb.isOnMission() && amb.isRegistered()) {
				assignment.setFinished();
			}

			return assignment;
		}
	}
}
